#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
新闻助手 - 使用Newspaper3k抓取新闻，按"主题->来源"两级分类并生成Markdown日报
"""
import uuid
import requests
import qwen_llm
import newspaper
from datetime import datetime
import re
from web_loader import load_web_content

# 定义新闻源URL列表
NEWS_SOURCES = [
    # 'https://www.bbc.com/news',  # BBC新闻
    # 'https://www.reuters.com/',  # 路透社
    # 'https://www.theguardian.com/',  # 卫报
    # 'https://edition.cnn.com/',         # CNN (暂时注释以提高速度)
    # 'https://www.nytimes.com/',         # 纽约时报 (暂时注释以提高速度)
    # 'http://www.chinadaily.com.cn/',  # 中国日报
    'https://www.xinhuanet.com/',  # 新华网
    'https://www.globaltimes.cn/',      # 环球时报 (暂时注释以提高速度)
]

# 定义网站名称映射
SOURCE_NAMES = {
    'bbc.com': 'BBC新闻',
    'reuters.com': '路透社',
    'theguardian.com': '卫报',
    'cnn.com': 'CNN',
    'nytimes.com': '纽约时报',
    'chinadaily.com.cn': '中国日报',
    'xinhuanet.com': '新华网',
    'globaltimes.cn': '环球时报'
}

# # 定义关键词分类
# KEYWORDS = {
#     '科技': ['科技', 'tech', 'technology', '人工智能', 'AI', '互联网', 'digital', 'innovation', 'computer', 'software'],
#     '金融': ['金融', 'finance', '经济', 'economy', '股市', 'market', '银行', 'bank', 'investment', 'trading'],
#     '教育': ['教育', 'education', '学校', 'school', '大学', 'university', '学生', 'student', 'learning'],
#     '健康': ['健康', 'health', '医疗', 'medical', '疾病', 'disease', '疫苗', 'vaccine', 'hospital', 'doctor'],
# }

# 改进：定义带权重的关键词（关键词, 权重） + 标题有限。即标题中出现的关键词，权重翻倍
# 权重越高，对分类影响越大（核心词权重3-5，相关词1-2）
KEYWORDS_WITH_WEIGHT = {
    '科技': [
        ('人工智能', 5), ('AI', 5),  # 核心关键词，权重最高
        ('科技', 4), ('technology', 4),
        ('互联网', 3), ('digital', 3),
        ('innovation', 2), ('computer', 2), ('software', 2)  # 相关词，权重稍低
    ],
    '金融': [
        ('金融', 5), ('finance', 5),
        ('股市', 4), ('market', 4),
        ('经济', 3), ('economy', 3),
        ('银行', 2), ('bank', 2), ('investment', 2), ('trading', 2)
    ],
    '教育': [
        ('教育', 5), ('education', 5),
        ('学校', 4), ('school', 4),
        ('大学', 3), ('university', 3),
        ('学生', 2), ('student', 2), ('learning', 2)
    ],
    '健康': [
        ('健康', 5), ('health', 5),
        ('医疗', 4), ('medical', 4),
        ('疾病', 3), ('disease', 3),
        ('疫苗', 2), ('vaccine', 2), ('hospital', 2), ('doctor', 2)
    ],
}

class NewsLoad:
    # 初始化配置
    def __init__(self):
        # 存储所有搜集到的文章信息
        self.articles = []
        # 存储按主题和来源分类后的文章
        self.categorized_articles = {}
        # 存储大模型生成的今日导览摘要
        self.today_summary = ""

    # 清除数据
    def clear_data(self):
        """清理所有新闻数据，重置到初始状态"""
        self.articles.clear()
        self.categorized_articles.clear()
        self.today_summary = ""

    """
    从预定义的新闻源收集新闻文章
    """
    def collect_news(self):
        print("开始收集新闻...")

        for source_url in NEWS_SOURCES:
            try:
                print(f"正在处理新闻源: {source_url}")
                """
                Newspaper3k框架的核心函数之一，用于从给定的新闻网站URL构建一个新闻源对象
                主要功能：
                    解析网站的RSS源或者爬取网站结构来发现文章
                    提取网站上的所有文章链接
                    构建一个包含所有文章对象的源对象
                参数说明：
                    source_url: 新闻网站的URL
                    memoize_articles=False: 禁用文章缓存功能，确保每次都获取最新的文章而不是使用缓存
                    request_timeout=5: 设置请求超时时间为5秒，防止在网络连接较慢时程序长时间等待
                """
                # paper = newspaper.build(source_url, memoize_articles=False, request_timeout=5)
                paper = newspaper.build(
                    source_url,
                    memoize_articles=False,
                    request_timeout=10,  # 增加超时时间
                    fetch_images=False,  # 关闭图片下载以提高速度
                    extract_body=True,  # 强制提取正文
                    # language='zh'  # 指定中文语言
                )
                # 限制每源最多5篇文章以提高速度。paper.articles 是一个包含该新闻源所有文章对象的列表
                articles = paper.articles[:5]

                print(f"已找到 {len(articles)} 篇文章，具体内容为：" + str(articles))

                """
                Newspaper3k的两阶段处理流程：
                    构建阶段 (newspaper.build())：
                        只是获取了文章的URL列表
                        文章对象此时只包含链接信息，没有实际内容
                        类似于获取目录或索引
                    下载阶段 (article.download())：
                        真正从网络获取文章的HTML内容
                        下载完整的网页数据
                        这是获取文章具体内容的必要步骤
                """

                for article in articles:
                    try:
                        # 下载文章的HTML内容
                        article.download()
                        # 解析HTML，提取标题、正文、发布日期等内容
                        article.parse()
                        # 检查文本内容
                        if not article.text:
                            print(f"警告: newspaper框架解析文章 '{article.title}' 的文本为空")
                            print("使用替代方法........")
                            # 加载网页内容。
                            article.text = load_web_content(article.url)
                        print(f"文章正文: {article.text}")
                        # 启用nlp步骤以获取关键词和摘要
                        # try:
                        #     article.nlp()
                        #     keywords = list(article.keywords) if article.keywords else []
                        #     """
                        #     这是一个条件表达式（三元运算符）
                        #     如果 article.keywords 存在且不为空，则将其转换为列表类型
                        #     如果 article.keywords 不存在或为空，则返回空列表 []
                        #     """
                        #     summary = article.summary if article.summary else ''
                        #     """
                        #     这也是一个条件表达式
                        #     如果 article.summary 存在且不为空，则直接使用该摘要
                        #     如果 article.summary 不存在或为空，则返回空字符串 ''
                        #     """
                        # except Exception as e:
                        #     print(f"NLP处理失败 {article.url}: {str(e)}")
                        #     keywords = []
                        #     summary = ''

                        # 保存文章信息
                        self.articles.append({
                            # 'id': f"news_{uuid.uuid4().hex[:8]}",  # 唯一标识。输出示例: news_a1b2c3d4
                            'title': article.title,
                            'url': article.url,
                            'text': article.text,
                            # 'keywords': keywords,
                            # 'summary': summary,
                            'publish_date': article.publish_date,
                            'source_url': source_url  # 添加来源URL信息
                        })
                    except Exception as e:
                        print(f"处理文章时出错 {article.url}: {str(e)}")
                        continue

            except Exception as e:
                print(f"处理新闻源 {source_url} 时出错: {str(e)}")
                continue

        print(f"共收集到 {len(self.articles)} 篇文章")

    """
    根据"主题->来源"两级分类对文章进行分类
    """
    # def categorize_articles(self):
    #     print("开始对文章进行分类...")
    #
    #     # 初始化分类结构：主题 -> 来源 -> 文章列表
    #     self.categorized_articles = {category: {} for category in KEYWORDS.keys()}
    #
    #     # 对每篇文章进行分类
    #     for article in self.articles:
    #         title = article['title'].lower()
    #         text = article['text'].lower()
    #         # keywords = ' '.join(article['keywords']).lower() if article['keywords'] else ''
    #         # summary = article['summary'].lower() if article['summary'] else ''
    #
    #         # 组合所有文本内容用于分类
    #         # full_text = f"{title} {text} {keywords} {summary}"
    #         full_text = f"{title} {text}"
    #
    #         source_url = article['source_url']
    #
    #         # 获取新闻源的域名。
    #         domain = re.search(r'https?://(?:www\.)?([^/]+)', source_url)
    #         if domain:
    #             # 例如从 https://www.bbc.com/news 提取 bbc.com
    #             domain_name = domain.group(1)
    #             # 获取新闻源的映射名称
    #             source_name = SOURCE_NAMES.get(domain_name, domain_name)
    #
    #             # 检查每个分类的关键词的匹配度
    #             best_category = None
    #             best_match_count = 0
    #
    #             for category, category_keywords in KEYWORDS.items():
    #                 # KEYWORDS.items() 是Python字典的一个方法，用于获取字典中的所有键值对。
    #                 # items() 是Python字典对象的一个内置方法，它返回一个包含字典所有键值对的视图对象，其中每个键值对都以元组的形式表示。
    #
    #                 # 转换关键词为小写进行匹配
    #                 category_keywords_lower = [kw.lower() for kw in category_keywords]
    #
    #                 # 计算匹配的关键词数量
    #                 match_count = sum(1 for keyword in category_keywords_lower if keyword in full_text)
    #                 """
    #                 生成器表达式
    #                     1 - 表达式部分，对于每个满足条件的元素，生成器会产出数字1
    #                     keyword in category_keywords_lower - 迭代部分，遍历 category_keywords_lower 列表中的每个关键词
    #                     if keyword in full_text - 条件部分，只有当关键词在 full_text 中时才满足条件
    #                 sum() 函数会对生成器表达式产出的所有值进行求和。
    #
    #                 假设：
    #                     category_keywords_lower = ['科技', 'tech', '人工智能', 'computer']
    #                     full_text 包含 '科技' 和 '人工智能'，但不包含 'tech' 和 'computer'
    #                 执行过程：
    #                     '科技' in full_text → True → 生成器产出 1
    #                     'tech' in full_text → False → 不产出任何值
    #                     '人工智能' in full_text → True → 生成器产出 1
    #                     'computer' in full_text → False → 不产出任何值
    #                     最终生成器产出的序列是：1, 1
    #                     sum()函数计算：1 + 1 = 2
    #                     所以 match_count = 2
    #                 """
    #
    #                 # 选择匹配度最高的分类
    #                 if match_count > best_match_count:
    #                     best_match_count = match_count
    #                     best_category = category
    #
    #             # 如果找到匹配的分类，则归入该分类
    #             if best_category:
    #                 # 初始化来源分类（如果尚未存在）
    #                 if source_name not in self.categorized_articles[best_category]:
    #                     self.categorized_articles[best_category][source_name] = []
    #
    #                 # 添加文章到对应分类
    #                 self.categorized_articles[best_category][source_name].append(article)
    #
    #     # 移除空的分类
    #     empty_categories = []
    #     for category, sources in self.categorized_articles.items():
    #         if not sources:  # 如果该主题下没有来源
    #             empty_categories.append(category)
    #         else:
    #             # 检查该主题下是否有文章
    #             has_articles = any(articles for articles in sources.values())
    #             if not has_articles:
    #                 empty_categories.append(category)
    #     for category in empty_categories:
    #         del self.categorized_articles[category]
    #
    #     # 显示分类结果
    #     for category, sources in self.categorized_articles.items():
    #         total_articles = sum(len(articles) for articles in sources.values())
    #         print(f"{category} 类别包含 {total_articles} 篇文章")
    #         for source, articles in sources.items():
    #             print(f"  - {source}: {len(articles)} 篇文章")

    """
        根据"主题->来源"两级分类对文章进行分类
        改进：基于关键词加权 + 标题优先的分类逻辑
    """
    def categorize_articles(self):
        print("开始对文章进行分类...")

        # 初始化分类结构：主题 -> 来源 -> 文章列表
        self.categorized_articles = {category: {} for category in KEYWORDS_WITH_WEIGHT.keys()}

        for article in self.articles:
            # 提取标题和正文（转为小写统一匹配）
            title = article['title'].lower()
            text = article['text'].lower()
            source_url = article['source_url']

            # 提取域名作为来源标识
            domain = re.search(r'https?://(?:www\.)?([^/]+)', source_url)
            if not domain:
                continue  # 跳过无法识别来源的文章
            domain_name = domain.group(1)
            source_name = SOURCE_NAMES.get(domain_name, domain_name)

            # 计算每个类别的加权得分
            best_category = None
            max_score = 0

            # 迭代主题并计算得分
            for category, keywords in KEYWORDS_WITH_WEIGHT.items():
                current_score = 0
                for kw, weight in keywords:
                    kw_lower = kw.lower()
                    # 标题中的关键词权重翻倍（标题优先）
                    if kw_lower in title:
                        current_score += weight * 2  # 标题权重放大
                    # 正文中的关键词按原权重计算
                    if kw_lower in text:
                        current_score += weight

                # 更新最佳分类
                if current_score > max_score:
                    max_score = current_score
                    best_category = category

            # 只保留有有效得分的文章（过滤无匹配关键词的）
            if best_category and max_score > 0:
                # 初始化来源列表（如果不存在）
                if source_name not in self.categorized_articles[best_category]:
                    self.categorized_articles[best_category][source_name] = []
                # 添加文章到对应分类
                self.categorized_articles[best_category][source_name].append(article)
                # 优化 — 为每篇文章添加一个唯一的ID且后续能根据前缀快速定位
                article['id'] = f"{best_category}-{source_name}-{uuid.uuid4().hex[:8]}"
                """
                示例：
                    科技-新华网-a1b2c3d4
                    金融-路透社-12345678
                    健康-环球时报-abcdef12
                """

        # 移除空分类（无文章的主题）
        empty_categories = []
        for category, sources in self.categorized_articles.items():
            # 主题下无新闻源
            if not sources:
                empty_categories.append(category)
            else:
                # 检查该主题下是否有实际文章
                has_articles = any(len(articles) > 0 for articles in sources.values())
                """
                sources.values()的作用
                    获取字典中所有值的视图对象，例如：
                        sources = {
                            'BBC新闻': [article1, article2],
                            '路透社': [],
                            '新华网': [article3]
                        }
                        sources.values()  # dict_values([[article1, article2], [], [article3]])
                
                生成器表达式执行过程
                (len(articles) > 0 for articles in sources.values())
                    对每个文章列表检查长度是否大于0：
                        [article1, article2] → len([article1, article2]) > 0 → True
                        [] → len([]) > 0 → False
                        [article3] → len([article3]) > 0 → True
                        生成布尔值序列：True, False, True
                any()函数判断
                    只要有一个元素为True，就返回True：any([True, False, True])  # 返回 True
                    any() 函数具有短路求值（short-circuit evaluation）特性：
                        一旦遇到第一个 True 值，立即返回 True
                        不会继续检查剩余的元素
                """
                if not has_articles:
                    empty_categories.append(category)
        for category in empty_categories:
            del self.categorized_articles[category]

        # 显示分类结果
        for category, sources in self.categorized_articles.items():
            total_articles = sum(len(articles) for articles in sources.values())
            print(f"{category} 类别包含 {total_articles} 篇文章")
            for source, articles in sources.items():
                print(f"  - {source}: {len(articles)} 篇文章")

    """提取所有新闻标题，调用千问模型生成今日导览摘要"""
    def generate_today_summary(self):

        if not self.articles:
            print("暂无爬取的文章，无法生成摘要")
            self.today_summary = "今日暂无有效新闻数据，无法生成导览摘要。"
            return

        # 提取所有文章标题，按来源分组（增强摘要的“整体感”）
        title_by_source = {}
        for article in self.articles:
            # 获取文章来源名称
            source_url = article["source_url"]
            domain = re.search(r'https?://(?:www\.)?([^/]+)', source_url)
            source_name = SOURCE_NAMES.get(domain.group(1), "未知来源") if domain else "未知来源"

            # 按来源分组存储标题
            if source_name not in title_by_source:
                title_by_source[source_name] = []
            title_by_source[source_name].append(article["title"])

        print("所有新闻标题：" + str(title_by_source.values()))

        # 构造提示词（Prompt Engineering，确保摘要符合要求）
        prompt = f"""
        请基于以下今日各新闻来源的标题，生成一段300-500字的中文“今日导览摘要”，要求如下：
        1. 整体感：提炼当日新闻的主要趋势、关注焦点或舆论动向，不要分来源单独描述；
        2. 概括性：避免机械复述标题，需对标题内容进行整合、概括与逻辑串联；
        3. 流畅性：语言风格自然、连贯，符合新闻摘要的正式语气，无口语化表达；
        4. 完整性：覆盖科技、金融、教育、健康等主要领域（若有相关标题），不遗漏关键信息。

        今日新闻标题按来源分组如下：
        {title_by_source}

        请直接输出摘要内容，无需额外前缀或后缀。
        """

        # 调用千问模型生成摘要
        print("正在调用阿里云百炼千问模型生成今日导览摘要...")
        qwen_llm.call_qwen(prompt, self)

        # 处理模型生成失败的情况
        if not self.today_summary:
            self.today_summary = "今日新闻导览摘要生成失败，建议检查网络或阿里云API配置后重试。"
        print("今日导览摘要生成完成")

    """
    生成Markdown格式的日报
    """
    def generate_markdown_report(self):
        """先写入“今日导览摘要”，再写入分类新闻"""
        print("正在生成Markdown日报...")
        today = datetime.now().strftime('%Y-%m-%d')
        filename = f"news_report_{today}.md"

        with open(filename, 'w', encoding='utf-8') as f:
            # 1. 写入日报标题
            f.write(f"# 新闻日报 ({today})\n\n")

            # 2. 写入“今日导览摘要”模块（置于最前端）
            f.write("## 今日导览摘要\n\n")
            f.write(f"{self.today_summary}\n\n")
            f.write("---\n\n")  # 分割线，区分摘要与分类新闻

            # 3. 写入分类新闻（保持原有逻辑）
            for category, sources in self.categorized_articles.items():
                has_articles = any(articles for articles in sources.values())
                if has_articles:
                    f.write(f"## {category}\n\n")
                    for source, articles in sources.items():
                        if articles:
                            f.write(f"### {source}\n\n")
                            for i, article in enumerate(articles, 1):
                                # 处理标题中的特殊字符，避免Markdown格式冲突
                                title = article['title'].replace('[', '(').replace(']', ')').replace('\\', '')
                                url = article['url']
                                f.write(f"{i}. [{title}]({url})\n")
                            f.write('\n')
                    f.write('\n')

        print(f"日报已保存为: {filename}")
        return filename

    def run(self):
        """
        运行新闻助手
        """
        print("启动新闻助手...")
        self.collect_news()
        self.categorize_articles()
        self.generate_today_summary()
        filename = self.generate_markdown_report()
        print("新闻助手任务完成!")
        return filename

# 模块级单例
newsLoadInstance = NewsLoad()